package com.niiwoo.civet.account.service.local.trade;

import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageHelper;
import com.niiwoo.civet.account.dao.entity.*;
import com.niiwoo.civet.account.dao.mapper.*;
import com.niiwoo.civet.account.dto.request.ApplyWithholdRequestDTO;
import com.niiwoo.civet.account.dto.request.UpdateRechargeRequestDTO;
import com.niiwoo.civet.account.dto.response.WithholdProtocolResultDTO;
import com.niiwoo.civet.account.enums.OrderEndTypeEnum;
import com.niiwoo.civet.account.enums.WithholdOrderStatusEnum;
import com.niiwoo.civet.account.service.local.AccountService;
import com.niiwoo.civet.account.service.local.BankCardService;
import com.niiwoo.civet.account.service.local.withholdProtocol.WithholdProtocolService;
import com.niiwoo.tripod.lanmao.component.LanMaoDirectService;
import com.niiwoo.tripod.lanmao.enums.*;
import com.niiwoo.tripod.lanmao.request.DirectRechargeRequest;
import com.niiwoo.tripod.lanmao.request.QueryTransactionRequest;
import com.niiwoo.tripod.lanmao.response.QueryRechargeResponse;
import com.niiwoo.tripod.provider.exception.BizException;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * 代扣Facade
 * Created by xiaoyingping on 2018/4/24.
 */
@Service
@Slf4j
public class WithholdFacade {

    @Autowired
    private WithholdService withholdService;
    @Autowired
    private WithholdOrderService withholdOrderService;
    @Autowired
    private AccountService accountService;
    @Autowired
    private BankCardService bankCardService;
    @Autowired
    private LanMaoDirectService lanMaoDirectService;
    @Autowired
    private WithholdOrderMapperExt withholdOrderMapperExt;
    @Autowired
    private PayChannelMapperExt payChannelMapperExt;
    @Autowired
    private WithholdProtocolService withholdProtocolService;
    @Autowired
    private WithholdProtocolChannelMapperExt withholdProtocolChannelMapperExt;
    @Autowired
    private WithholdProtocolSignedMapperExt withholdProtocolSignedMapperExt;
    @Autowired
    private BankTypeMapperExt bankTypeMapperExt;
    @Autowired
    private RedissonClient redissonClient;

    private final String applyWithholdLock = "APPLY_WITHHOLD_LOCK:";
    private final long DEFAULT_LOCK_TIME_OUT = 15000L;


    /**
     * 提交代扣请求
     * @param applyWithholdRequestDTO
     * @return
     */
    public void applyWithhold(ApplyWithholdRequestDTO applyWithholdRequestDTO){
        Assert.notNull(applyWithholdRequestDTO.getAccountId(),"accountId不能为空");
        Assert.notNull(applyWithholdRequestDTO.getAmount(),"amount不能为空");
        Assert.notNull(applyWithholdRequestDTO.getOrderId(),"orderId不能为空");

        log.info("Start apply withhold :{}",JSON.toJSONString(applyWithholdRequestDTO));
        //查询orderId是否已存在
        RLock lock = redissonClient.getLock(applyWithholdLock +applyWithholdRequestDTO.getOrderId());
        try{
            try {
                if (!lock.tryLock(DEFAULT_LOCK_TIME_OUT, TimeUnit.MILLISECONDS)) {
                    throw new BizException("ACT_STAGE_GLOBAL_SERVER_BUSY");
                }
            } catch (Exception e) {
                throw new BizException("ACT_STAGE_GLOBAL_SERVER_BUSY", e);
            }

            WithholdOrder withholdOrder = withholdOrderService.getWithholdOrderByOrderId(applyWithholdRequestDTO.getOrderId());
            if(Objects.nonNull(withholdOrder)){
                log.info("orderId:{}已存在代扣订单中，直接返回", applyWithholdRequestDTO.getOrderId());
                return;
            }

            //查询账户信息
            AccountBase accountBase = accountService.loadAccountById(applyWithholdRequestDTO.getAccountId());
            RoutePayChannel routePayChannel = getPayChannel(accountBase.getUserId(),accountBase.getOrgId(),applyWithholdRequestDTO.getPayChannelId(),applyWithholdRequestDTO.getTradeSource(),accountBase);
            log.info("Route pay channel print result:{}",JSON.toJSONString(routePayChannel));

            withholdOrder = new WithholdOrder();
            withholdOrder.setAccountId(applyWithholdRequestDTO.getAccountId());
            withholdOrder.setUserId(accountBase.getUserId());
            withholdOrder.setAmount(applyWithholdRequestDTO.getAmount());
            withholdOrder.setOrderId(applyWithholdRequestDTO.getOrderId());
            withholdOrder.setBatchNo(applyWithholdRequestDTO.getBatchNo());
            withholdOrder.setBankCardId(routePayChannel.getBankCard().getId());
            withholdOrder.setBankCode(routePayChannel.getBankCard().getBankCode());
            withholdOrder.setMobileNo(routePayChannel.getBankCard().getMobileNo());
            withholdOrder.setIdCard(routePayChannel.getBankCard().getIdCard());
            withholdOrder.setPayChannelId(routePayChannel.getPayChannel().getId());
            withholdOrder.setPayChannelCode(routePayChannel.getPayChannel().getChannelCode());
            withholdOrder.setRechargeWay(routePayChannel.getRechargeWayEnum().getValue());
            withholdOrderService.addWithholdOrder(withholdOrder);

            //调用存管自动充值接口
            PayCompanyEnum payCompanyEnum = PayCompanyEnum.enumOf(routePayChannel.getPayChannel().getChannelCode().toUpperCase());
            this.invokeLanMaoDirectRecharge(accountBase.getAccountNo(), withholdOrder.getRequestNo(), withholdOrder.getAmount(), payCompanyEnum,routePayChannel.getRechargeWayEnum());
        }finally {
            lock.unlock();
        }
    }


    /**
     * 处理代扣结果异步通知
     * @param rechargeRequestDTO
     */
    public void processWithholdAsyncNotify(UpdateRechargeRequestDTO rechargeRequestDTO){
        log.info("代扣结果异步通知处理request:{}", JSON.toJSONString(rechargeRequestDTO));
        WithholdOrder withholdOrder = withholdOrderService.loadWithholdOrderByRequestNo(rechargeRequestDTO.getRequestNo());

        if("0".equals(rechargeRequestDTO.getResponseCodeEnum())){
            if(RechargeStatusEnum.SUCCESS.name().equals(rechargeRequestDTO.getRechargeStatusEnum())){
                //代扣成功处理
                withholdService.processWithholdSuccess(withholdOrder.getId(), OrderEndTypeEnum.AUTO);
                return;
            }
        }

        if(StringUtils.isNotEmpty(rechargeRequestDTO.getErrorCode())
                && !StringUtils.equals(rechargeRequestDTO.getErrorCode(),"100001")
                && !StringUtils.equals(rechargeRequestDTO.getErrorCode(),"100013")
                && !StringUtils.equals(rechargeRequestDTO.getErrorCode(),"00070006")){
            //代扣失败
            withholdService.processWithholdFail(withholdOrder.getId(), rechargeRequestDTO.getErrorCode(), rechargeRequestDTO.getErrorMessage(), OrderEndTypeEnum.AUTO);
            return;
        }

        log.info("不处理代扣结果，等待主动查询存管银行的结果");
    }


    /**
     * 向懒猫查询代扣结果Job
     */
    public void queryLanMaoWithholdResultJob(){
        log.info("queryLanMaoWithholdResultJob start");

        //查询处理中的代扣订单,每次最多查询2000条(距离当前时间5分钟内的订单不查询)
        WithholdOrderExample example = new WithholdOrderExample();
        WithholdOrderExample.Criteria criteria = example.createCriteria();
        criteria.andStatusEqualTo(WithholdOrderStatusEnum.HANDLING.getValue());
        criteria.andCreateTimeLessThan(new DateTime().minusMinutes(5).toDate());

        PageHelper.startPage(1, 2000);
        List<WithholdOrder> withholdOrderList = withholdOrderMapperExt.selectByExample(example);
        log.info("需要查询代扣结果的订单数:{}",withholdOrderList.size());

        for (WithholdOrder withholdOrder : withholdOrderList) {
            try {
                //调用懒猫代扣查询接口
                this.invokeLanMaoQueryRecharge(withholdOrder);
            } catch (Exception e) {
                log.error("", e);
            }
        }
    }










    //调用懒猫接口进行代扣
    private void invokeLanMaoDirectRecharge (String accountNo, String requestNo, BigDecimal amount, PayCompanyEnum payCompany,RechargeWayEnum rechargeWayEnum){
        DirectRechargeRequest request = new DirectRechargeRequest();
        request.setPlatformUserNo(accountNo);
        request.setRequestNo(requestNo);
        request.setAmount(amount);
        request.setExpectPayCompany(payCompany);
        request.setRechargeWay(rechargeWayEnum);

        log.info("调用懒猫代扣接口request:{}", JSON.toJSONString(request));
        try {
            lanMaoDirectService.directRecharge(request);
        } catch (Exception e) {
            throw new RuntimeException("调用懒猫代扣接口发生异常:"+JSON.toJSONString(request),e);
        }
    }


    //调用懒猫接口查询代扣结果
    private void invokeLanMaoQueryRecharge(WithholdOrder withholdOrder){
        //查询账户信息
        AccountBase accountBase = accountService.loadAccountById(withholdOrder.getAccountId());

        //查询充值结果
        QueryTransactionRequest request = new QueryTransactionRequest();
        request.setRequestNo(withholdOrder.getRequestNo());
        request.setPlatformUserNo(accountBase.getAccountNo());
        log.info("调用懒猫充值查询接口request:{}", JSON.toJSONString(request));
        CompletableFuture<QueryRechargeResponse> future =  lanMaoDirectService.queryRecharge(request);

        QueryRechargeResponse response;
        try {
            response = future.get();
            log.info("调用懒猫充值查询接口response:{},request:{}",JSON.toJSONString(response), JSON.toJSONString(request));
        } catch (Exception e) {
            throw new RuntimeException("调用懒猫充值查询接口发生异常:", e);
        }

        //处理充值结果
        if (Objects.isNull(response)){
            log.info("查询代扣结果返回空，不做处理, requestNo{}",withholdOrder.getRequestNo());
            return;
        }

        if(isRechargeSuccess(response)){
            //代扣成功
            withholdService.processWithholdSuccess(withholdOrder.getId(), OrderEndTypeEnum.FAIL);
            return;
        }

        if(StringUtils.isNotEmpty(response.getErrorCode())
                && !StringUtils.equals(response.getErrorCode(),"100001")
                && !StringUtils.equals(response.getErrorCode(),"100013")
                && !StringUtils.equals(response.getErrorCode(),"00070006")){
            if(StringUtils.equals(response.getErrorCode(),"100007") && new DateTime().minusMinutes(15).toDate().before(withholdOrder.getCreateTime())){
                log.info("在存管系统中查询不到代扣请求，但没有超过15分钟");
            }else {
                //代扣失败
                withholdService.processWithholdFail(withholdOrder.getId(), response.getErrorCode(), response.getErrorMessage(), OrderEndTypeEnum.FAIL);
            }

            return;
        }else if(isRechargeFailed(response)){
            //代扣失败
            withholdService.processWithholdFail(withholdOrder.getId(), response.getErrorCode(), response.getErrorMessage(), OrderEndTypeEnum.FAIL);
            return;
        }

        log.info("不处理代扣结果，等待再次主动查询存管银行的结果");
    }

    private boolean isRechargeSuccess(QueryRechargeResponse rechargeResponse){
        return  ResponseCodeEnum.SUCCESS.getValue().equals(rechargeResponse.getCode())
                && ResponseStatusEnum.SUCCESS.getValue().equals(rechargeResponse.getStatus())
                && rechargeResponse.getRecords()!=null
                && rechargeResponse.getRecords().size()>0
                && RechargeStatusEnum.SUCCESS.name().equals(rechargeResponse.getRecords().get(0).getStatus());
    }

    private boolean isRechargeFailed(QueryRechargeResponse rechargeResponse){
        return ResponseCodeEnum.SUCCESS.getValue().equals(rechargeResponse.getCode())
                && ResponseStatusEnum.SUCCESS.getValue().equals(rechargeResponse.getStatus())
                && rechargeResponse.getRecords()!=null
                && rechargeResponse.getRecords().size()>0
                && RechargeStatusEnum.FAIL.name().equals(rechargeResponse.getRecords().get(0).getStatus());
    }

    /**
     * 路由支付渠道
     * 渠道：协议支付(优先)
     *       代扣充值
     */
    public RoutePayChannel getPayChannel(String userId,String orgId,Long payChannelId,Byte tradeSource,AccountBase accountBase){
        RoutePayChannel routePayChannel = new RoutePayChannel();
        BankCard bankCard = bankCardService.loadBankCard(accountBase);
        if (Objects.isNull(bankCard)) {
            throw new BizException("ACC10005", "借款人银行卡信息不存在");
        }
        routePayChannel.setBankCard(bankCard);

        WithholdProtocolChannel withholdProtocolChannel = withholdProtocolChannelMapperExt.selectByBankTypeId(bankCard.getBankTypeId());
        BankType isSupperWithhold = bankTypeMapperExt.selectBankTypeByCodeIgnoreStatus(bankCard.getBankCode());

        if(tradeSource != null && tradeSource.equals((byte)1)){
            log.info("Assign user:{} Choose Proxy",userId);
            setProxyChannel(routePayChannel,bankCard.getBankTypeId(),payChannelId);
            return routePayChannel;
        }
        if(isSupperWithhold.getWithholdType().equals((byte)2)){
            WithholdProtocolResultDTO withholdProtocolResultDTO=withholdProtocolService.getWithholdProtocolResultForLocal(userId,orgId);
            if(withholdProtocolResultDTO.getResult() && withholdProtocolResultDTO.getExpire() == 1){
                log.info("userId:{} contract effective ! Apply withhold used for PROTOCOL ",userId);
                routePayChannel.setPayChannel(payChannelMapperExt.selectByPrimaryKey(withholdProtocolResultDTO.getPayChannelId()));
                routePayChannel.setRechargeWayEnum(RechargeWayEnum.PROTOCOL);
                return routePayChannel;
            }else if(withholdProtocolResultDTO.getResult() && withholdProtocolResultDTO.getExpire() == 0){
                log.info("userId:{} contract expire ! Apply withhold used for PROTOCOL,but will failure,attention !",userId);
                WithholdProtocolSigned useExpireSigned = getProtocolChannelDescForDefault(userId,bankCard); //useExpireSigned 不会可能为空，所以不需要再非空判断
                PayChannel choosePayChannel = payChannelMapperExt.selectByPrimaryKey(useExpireSigned.getPayChannelId());
                routePayChannel.setPayChannel(choosePayChannel);
                routePayChannel.setRechargeWayEnum(RechargeWayEnum.PROTOCOL);
                return routePayChannel;
            }else{
                log.error("userId:{} has not chose PayChannel !",userId);
                throw new BizException("ACC10006", "支付渠道不存在");
            }
        }else{
            log.info("Current user:{} bank card does not supper PROTOCOL ! Choose Proxy",userId);
            setProxyChannel(routePayChannel,bankCard.getBankTypeId(),payChannelId);
            return routePayChannel;
        }
    }

    private void setProxyChannel(RoutePayChannel routePayChannel,Long bankCardId,Long payChannelId){
        PayChannel payChannel = null;
        if(null == payChannelId){
            payChannel = payChannelMapperExt.selectWithHoldChannelByBank(bankCardId);
            if (Objects.isNull(payChannel)) {
                throw new BizException("ACC10006", "支付渠道不存在");
            }
        }else{
            payChannel = payChannelMapperExt.selectByPrimaryKey(payChannelId);
        }
        routePayChannel.setPayChannel(payChannel);
        routePayChannel.setRechargeWayEnum(RechargeWayEnum.PROXY);
    }

    private WithholdProtocolSigned getProtocolChannelDescForDefault(String userId,BankCard bankCard){
        WithholdProtocolSignedExample example = new WithholdProtocolSignedExample();
        WithholdProtocolSignedExample.Criteria criteria = example.createCriteria();
        criteria.andRealNameEqualTo(bankCard.getRealName());
        criteria.andIdCardEqualTo(bankCard.getIdCard());
        criteria.andMobileNoEqualTo(bankCard.getMobileNo());
        criteria.andBankAccountNoEqualTo(bankCard.getBankAccountNo());
        example.setOrderByClause("create_time desc");
        List<WithholdProtocolSigned> withholdProtocolSignedList = withholdProtocolSignedMapperExt.selectByExample(example);
        if(Objects.nonNull(withholdProtocolSignedList)){
            log.info("User:{} use expire signed protocol:{} ! ",userId,JSON.toJSONString(withholdProtocolSignedList.get(0)));
            return withholdProtocolSignedList.get(0);
        }else{
            log.info("User:{} has not signed !",userId);
            return null;
        }
    }


    //@AllArgsConstructor
    @Getter
    @Setter
    protected class RoutePayChannel{
        /**
         * 渠道选择：PROTOCOL (优先)
         *           PROXY
         */
        private RechargeWayEnum rechargeWayEnum;
        /**
         * 支付渠道：
         *         Id，channelCode，status
         */
        private PayChannel payChannel;

        /**
         * 用户绑卡信息
         */
        private BankCard bankCard;
    }


}
